#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/i2c.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/timer.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/delay.h>

#include "mpu6050_driver.h"

struct mpu6050_driver_dev{
	struct cdev mydev;
	struct mutex mtx;
	struct i2c_client *pclt;
};

struct mpu6050_device_dev{
	struct class *cls;
	struct device *pdev;
};

struct mpu6050_device_dev *pgmydev;
struct mpu6050_driver_dev *pgmydrv;

int major = 12;
int minor = 0;
int mpu6050_driver_num = 1;

int mpu6050_read_byte(struct i2c_client *pclt, unsigned char reg)
{
	int ret = 0;
	char txbuf[1] = {reg};
	char rxbuf[1] = {0};
	struct i2c_msg msg[2] = {
		{pclt->addr, 0, 1, txbuf},
		{pclt->addr, I2C_M_RD, 1, rxbuf},
	};
	ret = i2c_transfer(pclt->adapter, msg, ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("i2c_transfer error\n");
		return ret;
	}
	return rxbuf[0];
}

int mpu6050_write_byte(struct i2c_client *pclt, unsigned char reg, unsigned char val)
{
	int ret = 0;
	char txbuf[2] = {reg, val};
	struct i2c_msg msg[1] = {
		{pclt->addr, 0, 2, txbuf},
	};
	ret = i2c_transfer(pclt->adapter, msg, ARRAY_SIZE(msg));
	if(ret < 0)
	{
		printk("i2c_transfer error\n");
		return ret;
	}
	return 0;
}

int mpu6050_driver_open(struct inode *pnode, struct file *pfile)
{
	pfile->private_data = (void*)container_of(pnode->i_cdev, struct mpu6050_driver_dev, mydev);
	printk("mpu6050_driver_open is called\n");
	return 0;
}

int mpu6050_driver_close(struct inode *pnode, struct file *pfile)
{
	printk("mpu6050_driver_close is called\n");
	return 0;
}

long mpu6050_driver_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
	struct mpu6050_driver_dev *pmydev = (struct mpu6050_driver_dev*)pfile->private_data;
	union mpu6050_data data;
	switch(cmd){
		case GET_ACCEL:
			data.accel.x = (mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_XOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_XOUTL_REG);
			data.accel.y = (mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_YOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_YOUTL_REG);
			data.accel.z = (mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_ZOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_ACCEL_ZOUTL_REG);
			break;
		case GET_GYRO:
			data.gyro.x = (mpu6050_read_byte(pmydev->pclt, MPU_GYRO_XOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_GYRO_XOUTL_REG);
			data.gyro.y = (mpu6050_read_byte(pmydev->pclt, MPU_GYRO_YOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_GYRO_YOUTL_REG);
			data.gyro.z = (mpu6050_read_byte(pmydev->pclt, MPU_GYRO_ZOUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_GYRO_ZOUTL_REG);
			break;
		case GET_TEMP:
			data.temp = (mpu6050_read_byte(pmydev->pclt, MPU_TEMP_OUTH_REG)<<8) | mpu6050_read_byte(pmydev->pclt, MPU_TEMP_OUTL_REG);
			break;
		default:
			return -EINVAL;
	}

	if(copy_to_user((void*)arg, &data, sizeof(data)))
	{
		printk("copy_to_user failed\n");
		return -EFAULT;
	}

	return sizeof(data);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mpu6050_driver_open,
	.release = mpu6050_driver_close,
	.unlocked_ioctl = mpu6050_driver_ioctl,
};

int mpu6050_init(struct i2c_client *pclt)
{
	mpu6050_write_byte(pclt, MPU_PWR_MGMT1_REG, 0x80);
	udelay(2000);
	mpu6050_write_byte(pclt, MPU_PWR_MGMT1_REG, 0x00);
	mpu6050_write_byte(pclt, MPU_GYRO_CFG_REG, 0xF8);
	// mpu6050_write_byte(pclt, MPU_ACCEL_CFG_REG, 0x19);
	mpu6050_write_byte(pclt, MPU_ACCEL_CFG_REG, 0x01);
	mpu6050_write_byte(pclt, MPU_SAMPLE_RATE_REG, 0x07);
	mpu6050_write_byte(pclt, MPU_CFG_REG, 0x06);

	printk("mpu6050_driver_init finished!\n");
	return 0;
}

int mpu6050_driver_probe(struct i2c_client *pclt, const struct i2c_device_id *pid)
{
	int ret = 0;
	int i = 0;
	dev_t devno = MKDEV(major, minor);

	ret = register_chrdev_region(devno, mpu6050_driver_num, "mpu6050_driver");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno, minor, mpu6050_driver_num, "mpu6050_driver");
		if(ret)
		{
			printk("get devno failed!");
			return -1;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	printk("register succeed!(%d, %d)\n", major, minor);

	pgmydrv = kmalloc(sizeof(struct mpu6050_driver_dev), GFP_KERNEL);
	if(!pgmydrv)
	{
		unregister_chrdev_region(devno, mpu6050_driver_num);
		printk("kmalloc pgmydrv failed\n");
		return -1;
	}
	memset(pgmydrv, 0, sizeof(struct mpu6050_driver_dev));
	
	pgmydrv->pclt = pclt;

	for(i=0; i<mpu6050_driver_num; i++)
	{
		devno = MKDEV(major, minor+i);
		cdev_init(&pgmydrv->mydev, &myops);
		pgmydrv->mydev.owner = THIS_MODULE;
		cdev_add(&pgmydrv->mydev, devno, 1);
	}

	pgmydev = kmalloc(sizeof(struct mpu6050_device_dev), GFP_KERNEL);
	if(!pgmydev)
	{
		cdev_del(&pgmydrv->mydev);
		unregister_chrdev_region(devno, mpu6050_driver_num);
		printk("kmalloc pgmydev failed\n");
		return -1;
	}

	pgmydev->cls = class_create(THIS_MODULE, "fsmpu6050");
	if(pgmydev->cls == NULL)
	{
		printk("class_create failed\n");
		cdev_del(&pgmydrv->mydev);
		unregister_chrdev_region(devno, mpu6050_driver_num);
	}

	pgmydev->pdev = device_create(pgmydev->cls, NULL, devno, NULL, "fsmpu6050%d", 0);
	if(pgmydev->pdev == NULL)
	{
		printk("device_create failed\n");
		class_destroy(pgmydev->cls);
		cdev_del(&pgmydrv->mydev);
		unregister_chrdev_region(devno, mpu6050_driver_num);
	}
	
	mutex_init(&pgmydrv->mtx);
	mpu6050_init(pgmydrv->pclt);

	return 0;
}

int mpu6050_driver_remove(struct i2c_client *pclt)
{
	dev_t devno = MKDEV(major, minor);
	device_destroy(pgmydev->cls, devno);
	class_destroy(pgmydev->cls);
	cdev_del(&pgmydrv->mydev);
	unregister_chrdev_region(devno, mpu6050_driver_num);
	kfree(pgmydrv);
	kfree(pgmydev);
	pgmydrv = NULL;
	pgmydev = NULL;
	printk("mpu6050_driver_remove is called\n");
	return 0;
}

struct of_device_id mpu6050_dts[] = {
	{.compatible = "invensense,mpu6050"},
	{}
};

struct i2c_device_id mpu6050_ids[] = {
	[0] = {"mpu6050", 0},
	[1] = {}
};

struct i2c_driver mpu6050_driver = {
	.driver = {
		.name = "mpu6050",
		.owner = THIS_MODULE,
		.of_match_table = mpu6050_dts
	},
	.probe = mpu6050_driver_probe,
	.remove = mpu6050_driver_remove,

	.id_table = mpu6050_ids
};

module_i2c_driver(mpu6050_driver);
MODULE_LICENSE("GPL");

